﻿using System;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;

namespace Gmantis.Controls.Extended
{
    [TemplatePart(Name = "Pad", Type = typeof(Canvas)), 
    TemplatePart(Name = "Hue", Type = typeof(Slider)), 
    TemplatePart(Name = "Indicator", Type = typeof(FrameworkElement)), 
    TemplatePart(Name = "Alpha", Type = typeof(Slider))]
    public class SpectrumColorPicker : Control
    {
        // Fields
        private byte _alpha = 0xff;
        internal Slider _elementAlpha;
        internal Slider _elementHue;
        internal FrameworkElement _elementIndicator;
        internal Canvas _elementPad;
        private HslColor _hslColor = new HslColor();
        internal bool _isLoaded;
        private bool _throwColorChanged = true;
        internal const string AlphaElementName = "Alpha";
        public static readonly DependencyProperty ColorProperty = DependencyProperty.Register("Color", typeof(Color), typeof(SpectrumColorPicker), new PropertyMetadata(new PropertyChangedCallback(SpectrumColorPicker.OnColorPropertyChanged)));
        internal const string HueElementName = "Hue";
        internal const string IndicatorElementName = "Indicator";
        internal const string PadElementName = "Pad";
        public static readonly DependencyProperty ShowAlphaChannelProperty = DependencyProperty.Register("ShowAlphaChannel", typeof(bool), typeof(SpectrumColorPicker), new PropertyMetadata(true));

        // Events
        public event EventHandler<PropertyChangedEventArgs<Color>> ColorChanged;

        // Methods
        public SpectrumColorPicker()
        {
            base.DefaultStyleKey = typeof(SpectrumColorPicker);
        }

        private float GetBrightness(float saturation, float luminance)
        {
            return (luminance / (1f - (saturation * 0.5f)));
        }

        private float GetLuminance(float brightness, float saturation)
        {
            return (brightness * (1f - (saturation * 0.5f)));
        }

        private T GetTemplateChild<T>(string childName, bool required, ref string errors) where T : class
        {
            DependencyObject templateChild = base.GetTemplateChild(childName);
            ApplyTemplateHelper.VerifyTemplateChild(typeof(T), "template part", childName, templateChild, required, ref errors);
            return (templateChild as T);
        }

        private static Storyboard GetTemplateChildResource(FrameworkElement root, string resourceName, bool required, ref string errors)
        {
            return GetTemplateChildResource<Storyboard>(root, resourceName, required, ref errors);
        }

        private static T GetTemplateChildResource<T>(FrameworkElement root, string resourceName, bool required, ref string errors) where T : class
        {
            object child = root.Resources[resourceName];
            ApplyTemplateHelper.VerifyTemplateChild(typeof(T), "resource", resourceName, child, required, ref errors);
            return (child as T);
        }

        private void InitializeAlphaPart()
        {
            this._elementAlpha.ValueChanged += delegate(object s, RoutedPropertyChangedEventArgs<double> e)
            {
                this._alpha = (byte)((int)Math.Round((double)(e.NewValue * 255.0), 0));
                this.Color = this._hslColor.ToColor(this._alpha);
            };
        }

        private void InitializeHuePart()
        {
            this._elementHue.ValueChanged += delegate(object s, RoutedPropertyChangedEventArgs<double> e)
            {
                this._hslColor.Hue = (float)this._elementHue.Value;
                this.Color = this._hslColor.ToColor(this._alpha);
            };
        }

        private void InitializeIndicatorPart()
        {
            this._elementIndicator.IsHitTestVisible = false;
            this.Update();
        }

        private void InitializePadPart()
        {
            this._elementPad.Background = new SolidColorBrush(Colors.Transparent);
            this._elementPad.Cursor = Cursors.Hand;
            this._elementPad.SizeChanged += delegate(object s, SizeChangedEventArgs e)
            {
                RectangleGeometry geometry = new RectangleGeometry
                {
                    Rect = new Rect(0.0, 0.0, e.NewSize.Width, e.NewSize.Height)
                };
                this._elementPad.Clip = geometry;
                this.Update();
            };
            TapHelper helper = new TapHelper(this._elementPad, false);
            DragHelper helper2 = new DragHelper(this._elementPad, DragHelperMode.TranslateXY, 1.0, true, false, false, false);
            helper2.DragDelta += delegate(object s, DragDeltaEventArgs e)
            {
                UIElement relativeTo = this.GetRootVisual();
                Point newPoint = relativeTo.TransformToVisual(this._elementPad).Transform(e.GetPosition(relativeTo));
                this.SetLumSatFromPoint(newPoint);
                base.Focus();
            };
            helper.Tapped += delegate(object s, TappedEventArgs e)
            {
                Point position = e.GetPosition(this._elementPad);
                this.SetLumSatFromPoint(position);
                base.Focus();
            };
            base.KeyDown += delegate(object s, System.Windows.Input.KeyEventArgs e)
            {
                double x = Canvas.GetLeft(this._elementIndicator);
                double y = Canvas.GetTop(this._elementIndicator);
                switch (e.Key)
                {
                    case Key.Left:
                        x = Math.Max((double)(x - 1.0), (double)0.0);
                        break;

                    case Key.Up:
                        y = Math.Max((double)(y - 1.0), (double)0.0);
                        break;

                    case Key.Right:
                        x = Math.Min(x + 1.0, this._elementPad.ActualWidth);
                        break;

                    case Key.Down:
                        y = Math.Min(y + 1.0, this._elementPad.ActualHeight);
                        break;
                }
                this.SetLumSatFromPoint(new Point(x, y));
            };
            this.Update();
        }

        public override void OnApplyTemplate()
        {
            string errors = string.Empty;
            base.OnApplyTemplate();
            this._isLoaded = true;
            this._elementPad = this.GetTemplateChild<Canvas>("Pad", true, ref errors);
            if (this._elementPad != null)
            {
                this.InitializePadPart();
            }
            this._elementIndicator = this.GetTemplateChild<FrameworkElement>("Indicator", true, ref errors);
            if (this._elementIndicator != null)
            {
                this.InitializeIndicatorPart();
            }
            this._elementHue = this.GetTemplateChild<Slider>("Hue", true, ref errors);
            if (this._elementHue != null)
            {
                this.InitializeHuePart();
            }
            this._elementAlpha = this.GetTemplateChild<Slider>("Alpha", false, ref errors);
            if (this._elementAlpha != null)
            {
                this.InitializeAlphaPart();
            }
            if (!string.IsNullOrEmpty(errors))
            {
                this._isLoaded = false;
                if ((base.Template != null) && !PlatformIndependent.IsInDesignMode(this))
                {
                    throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "Template cannot be applied to SpectrumColorPicker.\nDetails: {0}", new object[] { errors }));
                }
            }
        }

        private void OnColorChanged(Color oldValue)
        {
            this._hslColor = new HslColor(this.Color);
            this._alpha = this.Color.A;
            this.Update();
        }

        private static void OnColorPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            SpectrumColorPicker sender = d as SpectrumColorPicker;
            Color oldValue = (Color)e.OldValue;
            sender.OnColorChanged(oldValue);
            if ((sender.ColorChanged != null) && sender._throwColorChanged)
            {
                PropertyChangedEventArgs<Color> args = new PropertyChangedEventArgs<Color>
                {
                    OldValue = (Color)e.OldValue,
                    NewValue = (Color)e.NewValue
                };
                sender.ColorChanged(sender, args);
            }
        }

        private void SetLumSatFromPoint(Point newPoint)
        {
            newPoint.X = (newPoint.X < 0.0) ? 0.0 : ((newPoint.X > this._elementPad.ActualWidth) ? this._elementPad.ActualWidth : newPoint.X);
            newPoint.Y = (newPoint.Y < 0.0) ? 0.0 : ((newPoint.Y > this._elementPad.ActualHeight) ? this._elementPad.ActualHeight : newPoint.Y);
            Canvas.SetLeft(this._elementIndicator, newPoint.X);
            Canvas.SetTop(this._elementIndicator, newPoint.Y);
            this._hslColor.Saturation = (float)(newPoint.X / this._elementPad.ActualWidth);
            this._hslColor.Luminance = this.GetLuminance((float)((this._elementPad.ActualHeight - newPoint.Y) / this._elementPad.ActualHeight), this._hslColor.Saturation);
            this.Color = this._hslColor.ToColor(this._alpha);
        }

        private void Update()
        {
            this.BeginInvoke(delegate
            {
                if (this._elementHue != null)
                {
                    this._elementHue.Value = this._hslColor.Hue;
                }
                if ((this._elementIndicator != null) && (this._elementPad != null))
                {
                    double length = this._hslColor.Saturation * this._elementPad.ActualWidth;
                    float brightness = this.GetBrightness(this._hslColor.Saturation, this._hslColor.Luminance);
                    double num3 = this._elementPad.ActualHeight - (this._elementPad.ActualHeight * brightness);
                    Canvas.SetLeft(this._elementIndicator, length);
                    Canvas.SetTop(this._elementIndicator, num3);
                }
                if (this._elementAlpha != null)
                {
                    this._elementAlpha.Value = ((double)this._alpha) / 255.0;
                    if (this._elementAlpha.Background is GradientBrush)
                    {
                        (this._elementAlpha.Background as GradientBrush).GradientStops[0].Color = Color.FromArgb(0xff, this.Color.R, this.Color.G, this.Color.B);
                    }
                }
            });
        }

        // Properties
        public Color Color
        {
            get { return (Color)GetValue(ColorProperty); }
            set { SetValue(ColorProperty, value); }
        }

        public bool ShowAlphaChannel
        {
            get { return (bool)GetValue(ShowAlphaChannelProperty); }
            set { SetValue(ShowAlphaChannelProperty, value); }
        }
    }
}
